package com.xiaotao.server;

import com.alibaba.fastjson.JSONObject;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Component;

import javax.websocket.*;
import javax.websocket.server.PathParam;
import javax.websocket.server.ServerEndpoint;
import java.io.IOException;
import java.util.concurrent.ConcurrentHashMap;

//为 ConcurrentHashMap<String,WebSocketServer> 加入一个会话userId
@ServerEndpoint("/chatWebSocket/{userId}")
@Component
@Slf4j
public class WebSocketServer {

    /**
     *  [关于@OnOpen、@OnMessage、@OnClose、@OnError 中 Session session 的用意]
     *
     *  Session session: 主要用于代表一个单独的 WebSocket 连接会话.每当一个 WebSocket 客户端与服务器端点建立连接时，都会创建一个新的 Session 实例
     *      标识连接：每个 Session 对象都有一个唯一的 ID，可以用来识别和跟踪每个单独的连接。   ——>     可以使用 session.getId() 方法来获取这个 ID.对于日志记录、跟踪用户会话等方面非常有用。
     *      管理连接：可以通过 Session 对象来管理对应的 WebSocket 连接，例如发送消息给客户端、关闭连接等    ——>     session.getBasicRemote().sendText(message) 同步地发送文本消息，
     *                                                                                                 或者使用 session.getAsyncRemote().sendText(message) 异步地发送.可以调用 session.close() 来关闭 WebSocket 连接。
     *      获取连接信息：Session 对象提供了方法来获取连接的详细信息，比如连接的 URI、用户属性等。    ——>     可以使用 session.getRequestURI() 获取请求的 URI
     * **/

    //存储所有用户会话
    //ConcurrentHashMap<String,WebSocketServer> 中String 键（String类型）通常是用户ID或其他唯一标识符。允许服务器通过这个唯一标识符快速定位到对应的 WebSocketServer 实例，从而进行消息发送、接收或其他与特定客户端相关的操作
    //ConcurrentHashMap<String,WebSocketServer> 中为什么写 WebSocketServer 而不是其他，因为 WebSocketServer 作为一个实例，用于存储每个客户端连接。
    //所以在接下来@Onopen等使用中，当使用 ConcurrentHashMap<String,WebSocketServer> 时候，就不能单独使用 session， 需要添加一个诸如 userId 这样的会话来作为键。
    private static ConcurrentHashMap<String,WebSocketServer> webSocketMap = new ConcurrentHashMap<>();
    private Session session;
    private String userId="";

    //建立连接时
    @OnOpen
    //获取会话userId
    //@PathParam: 是Java JAX-RS API（Java API for RESTful Web Services）的一部分，用于WebSocket和RESTful Web服务. 在WebSocket服务器端，@PathParam 注解用于提取客户端连接URL中的参数值。
    public void onOpen(Session session, @PathParam("userId") String userId){
        this.session = session;             //当前WebSocket连接的 Session 对象存储在 WebSocketServer 实例 【这样做是为了在后续的通信过程中（例如在处理消息、关闭连接时），您可以使用 this.session 来引用当前连接的 Session 对象。】
        this.userId = userId;               //存储前端传来的 userId;
        webSocketMap.put(userId,this);      //WebSocketServer 实例与用户userId关联，并将这个关联存储在 webSocketMap 中。【其中this: 指的是当前的 WebSocketServer 实例】
        log.info("会话id：" + session.getId() + "对应的会话用户：" + userId + "【进行链接】");
        log.info("【websocket消息】有新的连接, 总数:{}", webSocketMap.size());
    }

    //接收客户端消息
    @OnMessage
    public void onMessage(String message,Session session) throws IOException {
        //当从客户端接收到消息时调用
        log.info("会话id"+ session.getId() +"对应的会话用户：" + userId + "的消息:" + message);
        //修改 onMessage 方法来实现广播： 当服务器接收到消息时，不是只发送给消息的发送者，而是广播给所有连接的客户端。 ——> (实现群聊)
        //判断message传来的消息不为空时，才能在页面上进行显示
        if(message != null && !message.isEmpty()){
            JSONObject obj = new JSONObject();
            obj.put("userId", userId);
            obj.put("message", message);
            // 封装成 JSON (Java对象转换成JSON格式的字符串。)
            String json = new ObjectMapper().writeValueAsString(obj);
            for(WebSocketServer client :webSocketMap.values()){
                client.session.getBasicRemote().sendText(json);
            }
        }
    }

    //链接关闭时
    @OnClose
    public void onClose(Session session){
        //关闭浏览器时清除存储在 webSocketMap 中的会话对象。
        webSocketMap.remove(userId);
        log.info("会话id：" + session.getId() + "对应的会话用户：" + userId + "【退出链接】");
        log.info("【websocket消息】有新的连接, 总数:{}", webSocketMap.size());
    }


    //链接出错时
    @OnError
    public void onError(Session session,Throwable throwable){
        //错误提示
        log.error("出错原因" + throwable.getMessage());
        //抛出异常
        throwable.printStackTrace();
    }
}

